package metadata

import (
	"fmt"
	"strconv"
	"time"

	"github.com/buger/jsonparser"
	"github.com/romberli/das/internal/dependency/metadata"
	"github.com/romberli/das/pkg/util/deployment"
	utilmeta "github.com/romberli/das/pkg/util/metadata"
	"github.com/romberli/go-util/common"
	"github.com/romberli/go-util/constant"
)

const (
	mysqlServerClusterIDStruct        = "ClusterID"
	mysqlServerServerNameStruct       = "ServerName"
	mysqlServerServiceNameStruct      = "MySQLServerServiceName"
	mysqlServerHostIPStruct           = "HostIP"
	mysqlServerPortNumStruct          = "PortNum"
	mysqlServerDeploymentTypeStruct   = "DeploymentType"
	mysqlServerDeploymentRegionStruct = "DeploymentRegion"
	mysqlServerDeploymentOrgStruct    = "DeploymentOrg"
	mysqlServerNetworkZoneStruct      = "NetworkZone"
	mysqlServerVersionStruct          = "Version"
	mysqlServerDelFlagStruct          = "DelFlag"

	mysqlServerMySQLVersionJSON = "mysql_version"
	mysqlServerIsMasterJSON     = "is_master"
)

var _ metadata.MySQLServer = (*MySQLServerInfo)(nil)

// MySQLServerInfo is a struct map to table in the database
type MySQLServerInfo struct {
	metadata.MySQLServerRepo
	ID               int       `middleware:"id" json:"id"`
	ClusterID        int       `middleware:"cluster_id" json:"cluster_id"`
	ServerName       string    `middleware:"server_name" json:"server_name"`
	ServiceName      string    `middleware:"service_name" json:"service_name"`
	HostIP           string    `middleware:"host_ip" json:"host_ip"`
	PortNum          int       `middleware:"port_num" json:"port_num"`
	DeploymentType   int       `middleware:"deployment_type" json:"deployment_type"`
	DeploymentRegion string    `middleware:"deployment_region" json:"deployment_region"`
	DeploymentOrg    string    `middleware:"deployment_org" json:"deployment_org"`
	NetworkZone      string    `middleware:"network_zone" json:"network_zone"`
	DelFlag          int       `middleware:"del_flag" json:"del_flag"`
	CreateTime       time.Time `middleware:"create_time" json:"create_time"`
	LastUpdateTime   time.Time `middleware:"last_update_time" json:"last_update_time"`
}

// NewMySQLServerInfo returns a new MySQLServerInfo
func NewMySQLServerInfo(repo metadata.MySQLServerRepo,
	id int,
	clusterID int,
	serverName string,
	serviceName string,
	hostIP string,
	portNum int,
	deploymentType int,
	deploymentRegion string,
	deploymentOrg string,
	networkZone string,
	delFlag int,
	createTime, lastUpdateTime time.Time) metadata.MySQLServer {
	return newMySQLServerInfo(
		repo,
		id,
		clusterID,
		serverName,
		serviceName,
		hostIP,
		portNum,
		deploymentType,
		deploymentRegion,
		deploymentOrg,
		networkZone,
		delFlag,
		createTime,
		lastUpdateTime,
	)
}

// NewMySQLServerInfoWithGlobal returns a new MySQLServerInfo with default MySQLServerRepo
func NewMySQLServerInfoWithGlobal(
	id int,
	clusterID int,
	serverName string,
	serviceName string,
	hostIP string,
	portNum int,
	deploymentType int,
	deploymentRegion string,
	deploymentOrg string,
	networkZone string,
	delFlag int,
	createTime, lastUpdateTime time.Time) metadata.MySQLServer {
	return newMySQLServerInfo(
		NewMySQLServerRepoWithGlobal(),
		id,
		clusterID,
		serverName,
		serviceName,
		hostIP,
		portNum,
		deploymentType,
		deploymentRegion,
		deploymentOrg,
		networkZone,
		delFlag,
		createTime,
		lastUpdateTime,
	)
}

// NewEmptyMySQLServerInfoWithGlobal returns a new MySQLServerInfo with default MySQLServerRepo
func NewEmptyMySQLServerInfoWithGlobal() metadata.MySQLServer {
	return &MySQLServerInfo{MySQLServerRepo: NewMySQLServerRepoWithGlobal()}
}

// NewMySQLServerInfoWithDefault returns a new MySQLServerInfo with default MySQLServerRepo
func NewMySQLServerInfoWithDefault(
	clusterID int,
	serverName string,
	serviceName string,
	hostIP string,
	portNum int,
	deploymentType int,
	deploymentRegion string,
	deploymentOrg string,
	networkZone string) metadata.MySQLServer {
	return &MySQLServerInfo{
		MySQLServerRepo:  NewMySQLServerRepoWithGlobal(),
		ClusterID:        clusterID,
		ServerName:       serverName,
		ServiceName:      serviceName,
		HostIP:           hostIP,
		PortNum:          portNum,
		DeploymentType:   deploymentType,
		DeploymentRegion: deploymentRegion,
		DeploymentOrg:    deploymentOrg,
		NetworkZone:      networkZone,
	}
}

// NewMySQLServerInfoWithMapAndRandom returns a new metadata.MySQLServer with given map
func NewMySQLServerInfoWithMapAndRandom(fields map[string]interface{}) (metadata.MySQLServer, error) {
	msi := &MySQLServerInfo{}
	err := common.SetValuesWithMapAndRandom(msi, fields)
	if err != nil {
		return nil, err
	}
	return msi, nil
}

// newMySQLServerInfo returns a new MySQLServerInfo
func newMySQLServerInfo(repo metadata.MySQLServerRepo,
	id int,
	clusterID int,
	serverName string,
	serviceName string,
	hostIP string,
	portNum int,
	deploymentType int,
	deploymentRegion string,
	deploymentOrg string,
	networkZone string,
	delFlag int,
	createTime, lastUpdateTime time.Time) *MySQLServerInfo {
	return &MySQLServerInfo{
		repo,
		id,
		clusterID,
		serverName,
		serviceName,
		hostIP,
		portNum,
		deploymentType,
		deploymentRegion,
		deploymentOrg,
		networkZone,
		delFlag,
		createTime,
		lastUpdateTime,
	}
}

// Identity returns ID of entity
func (msi *MySQLServerInfo) Identity() int {
	return msi.ID
}

// GetClusterID returns the mysql cluster id
func (msi *MySQLServerInfo) GetClusterID() int {
	return msi.ClusterID
}

// GetServerName returns the server name
func (msi *MySQLServerInfo) GetServerName() string {
	return msi.ServerName
}

// GetServiceName returns the service name
func (msi *MySQLServerInfo) GetServiceName() string {
	return msi.ServiceName
}

// GetHostIP returns the host ip
func (msi *MySQLServerInfo) GetHostIP() string {
	return msi.HostIP
}

// GetPortNum returns the port number
func (msi *MySQLServerInfo) GetPortNum() int {
	return msi.PortNum
}

// GetDeploymentType returns the deployment type
func (msi *MySQLServerInfo) GetDeploymentType() int {
	return msi.DeploymentType
}

// GetDeploymentRegion returns the deployment region
func (msi *MySQLServerInfo) GetDeploymentRegion() string {
	return msi.DeploymentRegion
}

// GetDeploymentOrg returns the deployment organization
func (msi *MySQLServerInfo) GetDeploymentOrg() string {
	return msi.DeploymentOrg
}

// GetNetworkZone returns the network zone
func (msi *MySQLServerInfo) GetNetworkZone() string {
	return msi.NetworkZone
}

// GetDelFlag returns the delete flag
func (msi *MySQLServerInfo) GetDelFlag() int {
	return msi.DelFlag
}

// GetCreateTime returns created time of entity
func (msi *MySQLServerInfo) GetCreateTime() time.Time {
	return msi.CreateTime
}

// GetLastUpdateTime returns last updated time of entity
func (msi *MySQLServerInfo) GetLastUpdateTime() time.Time {
	return msi.LastUpdateTime
}

// GetMySQLVersionLocal gets the mysql version from the mysql server
func (msi *MySQLServerInfo) GetMySQLVersionLocal() (string, error) {
	return msi.MySQLServerRepo.GetMySQLVersionLocal(msi.HostIP, msi.PortNum)
}

// GetMySQLVersionWithHTTP gets the mysql version from the mysql server with http
func (msi *MySQLServerInfo) GetMySQLVersionWithHTTP() (string, error) {
	respBody, err := msi.postDAS(utilmeta.MySQLServerGetVersionPath)
	if err != nil {
		return constant.EmptyString, err
	}

	return jsonparser.GetString(respBody, mysqlServerMySQLVersionJSON)
}

// IsMasterLocal returns if this mysql server is a master node
func (msi *MySQLServerInfo) IsMasterLocal() (bool, error) {
	return msi.MySQLServerRepo.IsMasterLocal(msi.GetHostIP(), msi.GetPortNum())
}

// IsMasterWithHTTP returns if this mysql server is a master node with http
func (msi *MySQLServerInfo) IsMasterWithHTTP() (bool, error) {
	respBody, err := msi.postDAS(utilmeta.MySQLServerIsMasterPath)
	if err != nil {
		return false, err
	}
	isMasterStr, err := jsonparser.GetString(respBody, mysqlServerIsMasterJSON)
	if err != nil {
		return false, err
	}
	isMaster, err := strconv.ParseBool(isMasterStr)
	if err != nil {
		return false, err
	}

	return isMaster, nil
}

// GetMySQLCluster gets mysql cluster of this server
func (msi *MySQLServerInfo) GetMySQLCluster() (metadata.MySQLCluster, error) {
	return msi.MySQLServerRepo.GetMySQLClusterByID(msi.Identity())
}

// GetMonitorSystem gets monitor system of this server
func (msi *MySQLServerInfo) GetMonitorSystem() (metadata.MonitorSystem, error) {
	return msi.MySQLServerRepo.GetMonitorSystem(msi.Identity())
}

// Set sets entity with given fields, key is the field name and value is the relevant value of the key
func (msi *MySQLServerInfo) Set(fields map[string]interface{}) error {
	for fieldName, fieldValue := range fields {
		err := common.SetValueOfStruct(msi, fieldName, fieldValue)
		if err != nil {
			return err
		}
	}

	return nil
}

// Delete sets DelFlag to true, need to use Save to write to the middleware
func (msi *MySQLServerInfo) Delete() {
	msi.DelFlag = 1
}

// MarshalJSON marshals entity to json string, it only marshals fields that has default tag
func (msi *MySQLServerInfo) MarshalJSON() ([]byte, error) {
	return common.MarshalStructWithTag(msi, constant.DefaultMarshalTag)
}

// MarshalJSONWithFields marshals only with specified fields of entity to json string
func (msi *MySQLServerInfo) MarshalJSONWithFields(fields ...string) ([]byte, error) {
	return common.MarshalStructWithFields(msi, fields...)
}

func (msi *MySQLServerInfo) postDAS(path string) ([]byte, error) {
	deploymentService := deployment.NewServiceWithDefault(msi)
	// prepare data
	data := []byte(fmt.Sprintf(utilmeta.HostInfoHTTPBodyTemplate, msi.GetHostIP(), msi.GetPortNum()))
	// call http api
	return deploymentService.PostDAS(path, data)
}
